#include "CAudioPlayer.h"

unsigned int _stdcall CAudioPlayer::ThreadStub(void* pData) {
	CAudioPlayer* thisClass = (CAudioPlayer*)pData;
	return thisClass->run();
}

CAudioPlayer::CAudioPlayer()
{
	m_bLoop = false;
	m_evtStatus.Create(false);
	m_Status = CAudioPlayer::Unknow;
}

CAudioPlayer::~CAudioPlayer()
{
	Release();
}

void CAudioPlayer::Release()
{
	Stop();

	if (m_pWav) {
		CoTaskMemFree(m_pWav);
		m_pWav = NULL;
	}
	SafeRelease(&m_pDSNotify);
	SafeRelease(&m_pDSBuffer8);
	SafeRelease(&m_pDSBuffer);
	SafeRelease(&m_pDS);
	SafeRelease(&m_pReader);
	m_Status = CAudioPlayer::Unknow;
}

HRESULT CAudioPlayer::Prepare(const WCHAR* wszFileName, HWND hwnd)
{
	Release();

	ERROR_RETURN(MFCreateSourceReaderFromURL(wszFileName, NULL, &m_pReader), "CreateSourceReader failed")

	IMFMediaType *pUncompressedAudioType = NULL;
	IMFMediaType *pPartialType = NULL;

	ERROR_RETURN(MFCreateMediaType(&pPartialType), "CreateMediaType failed");
	ERROR_RETURN(pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), "MediaType Set Major Type failed");
	ERROR_RETURN(pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM), "MediaType Set Sub Type failed");

	ERROR_RETURN(m_pReader->SetCurrentMediaType(
		(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
		NULL,
		pPartialType
	), "SetCurrentMediaType failed");
	
	ERROR_RETURN(m_pReader->GetCurrentMediaType(
		(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
		&pUncompressedAudioType
	), "GetCurrentMediaType failed");
	
	ERROR_RETURN(m_pReader->SetStreamSelection(
		(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
		TRUE
	), "SetStreamSelection failed");

	UINT32 cbFormat = 0;
	
	// Convert the PCM audio format into a WAVEFORMATEX structure.
	ERROR_RETURN(MFCreateWaveFormatExFromMFMediaType(pUncompressedAudioType, &m_pWav, &cbFormat), "MFCreateWaveFormatExFromMFMediaType failed") ;

	ERROR_RETURN(DirectSoundCreate8(NULL, &m_pDS, NULL), "Direct Sound Create failed");

	ERROR_RETURN(m_pDS->SetCooperativeLevel(hwnd, DSSCL_NORMAL), "SetCooperativeLevel failed");

	DSBUFFERDESC dsbd;
	memset(&dsbd, 0, sizeof(dsbd));
	dsbd.dwSize = sizeof(dsbd);
	dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
	dsbd.dwBufferBytes = MAX_AUDIO_BUF * m_pWav->nAvgBytesPerSec;
	dsbd.lpwfxFormat = m_pWav;

	ERROR_RETURN(m_pDS->CreateSoundBuffer(&dsbd, &m_pDSBuffer, NULL), "CreateSoundBuffer failed");

	ERROR_RETURN(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&m_pDSBuffer8), "QueryInterface IID_IDirectSoundBuffer8 failed");

	ERROR_RETURN(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&m_pDSNotify), "QueryInterface IID_IDirectSoundNotify");

	for (int i = 0; i<MAX_AUDIO_BUF; i++) {
		m_pDSPosNotify[i].dwOffset =  i * m_pWav->nAvgBytesPerSec;
		m_event[i] = ::CreateEvent(NULL, false, false, NULL);
		m_pDSPosNotify[i].hEventNotify = m_event[i];
	}
	ERROR_RETURN(m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF, m_pDSPosNotify), "SetNotificationPositions failed");
	SafeRelease(&m_pDSNotify);

	m_bCanseek = false;
	ULONG flags = 0;
	PROPVARIANT var;
	PropVariantInit(&var);

	ERROR_RETURN(m_pReader->GetPresentationAttribute(
		(DWORD)MF_SOURCE_READER_MEDIASOURCE,
		MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS,
		&var
	), "GetPresentationAttribute failed");

	ERROR_RETURN(PropVariantToUInt32(var, &flags), "PropVariantToUInt32 failed");

	if ((flags & MFMEDIASOURCE_CAN_SEEK) &&	!(flags & MFMEDIASOURCE_HAS_SLOW_SEEK))
	{
		m_bCanseek = TRUE;
	}

	m_Status = CAudioPlayer::Stopped;
}

unsigned int CAudioPlayer::run()
{
	LPVOID			buf = NULL;
	DWORD			buf_len = 0;
	DWORD			res = WAIT_OBJECT_0;
	DWORD			offset = 0;// m_pWav->nAvgBytesPerSec;

	LONGLONG		llTimestamp;
	DWORD			dwActualStreamIndex;
	DWORD			dwFlags = 0;
	IMFSample		*pSample = NULL;
	IMFMediaBuffer	*pBuffer = NULL;
	DWORD			cbAudioData = 0;
	BYTE			*pAudioData = NULL;
	
	LPVOID rest_buf = malloc(m_pWav->nAvgBytesPerSec);
	int    rest_len = 0;

	m_pDSBuffer8->SetCurrentPosition(0);
	m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);/*DSBPLAY_LOOPING*/
	//Loop  
	while (m_Status != CAudioPlayer::Stopped) {
		if (m_Status != CAudioPlayer::Playing) {
			m_pDSBuffer8->Stop();
			m_evtStatus.wait();
			if (m_Status == CAudioPlayer::Playing) {
				m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);/*DSBPLAY_LOOPING*/
			}
			else {
				continue;
			}

		}
		printf("%d\n", res);
		if ((res >= WAIT_OBJECT_0) && (res <= WAIT_OBJECT_0 + 3)) {
			ERROR_RETURN(m_pDSBuffer8->Lock(offset, m_pWav->nAvgBytesPerSec, &buf, &buf_len, NULL, NULL, 0), "DirectSound Buffer Lock failed");
			int max_len = buf_len;
			if (rest_len > 0) {
				max_len -= rest_len;
				memcpy(buf, rest_buf, rest_len);
				rest_len = 0;
			}
			if (m_nSeekTo >= 0) {
				rest_len = 0;
				SeekTo();
			}
			while (max_len > 0) {
				// Read the next sample.
				ERROR_RETURN(m_pReader->ReadSample(
					(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
					0,
					&dwActualStreamIndex,
					&dwFlags,
					&llTimestamp,
					&pSample
				), "ReadSample failed");
				if (pSample == NULL)
				{
					if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) {
						if (m_bLoop && m_bCanseek) {
							m_nSeekTo = 0;
							SeekTo();
						}
						else {
							m_Status = CAudioPlayer::Stopped;
							break;
						}
					}else if (dwFlags & MF_SOURCE_READERF_STREAMTICK) {

					}else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) {
						//m_Status = CAudioPlayer::Stopped;
						//break;
						ERROR_RETURN(Seek(0), "Seek Failed");
					}
					continue;
				}
				ERROR_RETURN(pSample->ConvertToContiguousBuffer(&pBuffer), "ConvertToContiguousBuffer failed");
				ERROR_RETURN(pBuffer->Lock(&pAudioData, NULL, &cbAudioData), "Buffer Lock failed");
				int len = (max_len < cbAudioData) ? max_len : cbAudioData;
				memcpy((byte*)buf + buf_len - max_len, pAudioData, len);
				//printf("buffer size:%d, %d, %d, %d \n", cbBuffer, len, buf_len, max_len);
				max_len -= len;
				if (len < cbAudioData) {
					rest_len = cbAudioData - len;
					memcpy(rest_buf, pAudioData + len, rest_len);
				}
				ERROR_RETURN(pBuffer->Unlock(), "Buffer Unlock failed");
				pAudioData = NULL;
				SafeRelease(&pBuffer);
				SafeRelease(&pSample);
			}
			ERROR_RETURN(m_pDSBuffer8->Unlock(buf, buf_len, NULL, 0), "DirectSound Buffer Unlock failed.");
			offset += buf_len;
			offset %= (m_pWav->nAvgBytesPerSec * MAX_AUDIO_BUF);
		}
		if (m_Status == CAudioPlayer::Stopped) {
			break;
		}
		res = WaitForMultipleObjects(MAX_AUDIO_BUF, m_event, FALSE, 1050);
	}
	if (rest_buf != NULL) {
		free(rest_buf);
	}
	if (m_bCanseek) {
		m_nSeekTo = 0;
		SeekTo();
	}
	m_pDSBuffer8->Stop();
	m_Status = CAudioPlayer::Stopped;
	return 0;
}

HRESULT CAudioPlayer::Play()
{
	switch(m_Status){
	case CAudioPlayer::Unknow:
		return S_FALSE;
	case CAudioPlayer::Playing:
	case CAudioPlayer::Paused:
		m_Status = CAudioPlayer::Playing;
		break;
	case CAudioPlayer::Stopped:
		if (m_thread) {
			delete 	m_thread;
			m_thread = NULL;
		}
		m_Status = CAudioPlayer::Playing;
		m_thread = new QKThread(ThreadStub, this);
		break;
	}
	m_evtStatus.signal();
	return S_OK;
}

HRESULT CAudioPlayer::Pause()
{
	switch (m_Status) {
	case CAudioPlayer::Unknow:
		return S_FALSE;
	case CAudioPlayer::Playing:
	case CAudioPlayer::Paused:
		m_Status = CAudioPlayer::Paused;
		break;
	case CAudioPlayer::Stopped:
		break;
	}
	return S_OK;

}

HRESULT CAudioPlayer::Stop()
{
	switch (m_Status) {
	case CAudioPlayer::Unknow:
		return S_FALSE;
	case CAudioPlayer::Playing:
	case CAudioPlayer::Paused:
		m_Status = CAudioPlayer::Stopped;
		m_evtStatus.signal();
		if (m_thread) {
			delete 	m_thread;
			m_thread = NULL;
		}
		break;
	case CAudioPlayer::Stopped:
		break;
	}
	return S_OK;
}

HRESULT CAudioPlayer::SeekTo()
{
	ERROR_RETURN(m_pReader->Flush((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM), "Flush failed");
	PROPVARIANT var;
	PropVariantInit(&var);
	var.vt = VT_I8;
	var.hVal.QuadPart = m_nSeekTo;
	ERROR_RETURN(m_pReader->SetCurrentPosition(GUID_NULL, var), "SetCurrentPosition failed");
	m_nSeekTo = -1;
}

HRESULT CAudioPlayer::Seek(LONGLONG position)
{
	if (m_Status == CAudioPlayer::Unknow) {
		return S_FALSE;
	}

	if (!m_bCanseek) {
		return S_FALSE;
	}

	m_nSeekTo = position;

	return S_OK;

}

HRESULT CAudioPlayer::SetLoop(bool loop)
{
	m_bLoop = loop;
	return S_OK;
}

HRESULT CAudioPlayer::SetVolume(long vol)
{
	return S_OK;
}

